package org.karelun.rabbit.rcp.editor;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.emf.cdo.eresource.CDOResource;
import org.eclipse.emf.cdo.transaction.CDOTransactionConflictEvent;
import org.eclipse.emf.cdo.transaction.CDOTransactionFinishedEvent;
import org.eclipse.emf.cdo.transaction.CDOTransactionStartedEvent;
import org.eclipse.emf.cdo.util.CDOUtil;
import org.eclipse.emf.cdo.view.CDOView;
import org.eclipse.emf.cdo.view.CDOViewInvalidationEvent;
import org.eclipse.emf.common.command.BasicCommandStack;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.edit.EMFEditPlugin;
import org.eclipse.emf.edit.provider.ComposedAdapterFactory;
import org.eclipse.emf.edit.provider.ReflectiveItemProviderAdapterFactory;
import org.eclipse.emf.edit.provider.resource.ResourceItemProviderAdapterFactory;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.net4j.util.event.IEvent;
import org.eclipse.net4j.util.event.IListener;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.views.properties.IPropertySheetPage;
import org.eclipse.ui.views.properties.tabbed.ITabbedPropertySheetPageContributor;
import org.eclipse.ui.views.properties.tabbed.TabbedPropertySheetPage;
import org.karelun.ideaboard.gef.DefaultEditDomain;
import org.karelun.ideaboard.gef.editparts.ScalableFreeformRootEditPart;
import org.karelun.ideaboard.gef.palette.ConnectionCreationToolEntry;
import org.karelun.ideaboard.gef.palette.CreationToolEntry;
import org.karelun.ideaboard.gef.palette.MarqueeToolEntry;
import org.karelun.ideaboard.gef.palette.PaletteDrawer;
import org.karelun.ideaboard.gef.palette.PaletteEntry;
import org.karelun.ideaboard.gef.palette.PaletteGroup;
import org.karelun.ideaboard.gef.palette.PaletteRoot;
import org.karelun.ideaboard.gef.palette.SelectionToolEntry;
import org.karelun.ideaboard.gef.palette.ToolEntry;
import org.karelun.ideaboard.gef.ui.parts.GraphicalEditorWithFlyoutPalette;
import org.karelun.ideaboard.gef.ui.parts.IGraphicalEditableProvider;
import org.karelun.rabbit.graph.Connection;
import org.karelun.rabbit.graph.Shape;
import org.karelun.rabbit.graph.provider.GraphItemProviderAdapterFactory;
import org.karelun.rabbit.rcp.editpart.PartFactory;
import org.karelun.whiteboard.gef.cdo.ITransactionEditingDomain;
import org.karelun.whiteboard.gef.cdo.TransactionAdapterFactoryEditingDomain;

public class RabbitEditor extends GraphicalEditorWithFlyoutPalette implements IGraphicalEditableProvider,ITabbedPropertySheetPageContributor {

	private CDOView cdoView;

	private ITransactionEditingDomain editingDomain;

	private ComposedAdapterFactory adapterFactory;

	private IPropertySheetPage propertySheetPage;

	private IListener cdoViewListener = new IListener() {
		public void notifyEvent(IEvent event) {
			if (event instanceof CDOViewInvalidationEvent) {
			} else if (event instanceof CDOTransactionStartedEvent
					|| event instanceof CDOTransactionFinishedEvent) {
				cdoViewDirtyStateChanged();
			} else if (event instanceof CDOTransactionConflictEvent) {

			}
		}
	};

	public RabbitEditor() {
		DefaultEditDomain ed = new DefaultEditDomain(this);
		ed.setCommandStack(new BasicCommandStack());
		setEditDomain(ed);
		ComposedAdapterFactory.Descriptor.Registry registry = EMFEditPlugin
				.getComposedAdapterFactoryDescriptorRegistry();
		adapterFactory = new ComposedAdapterFactory(registry);
		adapterFactory
				.addAdapterFactory(new ResourceItemProviderAdapterFactory());
		adapterFactory.addAdapterFactory(new GraphItemProviderAdapterFactory());
		adapterFactory
				.addAdapterFactory(new ReflectiveItemProviderAdapterFactory());
	}

	@Override
	public void init(IEditorSite site, IEditorInput input)
			throws PartInitException {
		super.init(site, input);
		createContents();
	}

	@Override
	protected void configureGraphicalViewer() {
		super.configureGraphicalViewer();
		getGraphicalViewer().setEditableProvider(this);
		ScalableFreeformRootEditPart rootEditPart = new ScalableFreeformRootEditPart();
		getGraphicalViewer().setRootEditPart(rootEditPart);
		getGraphicalViewer().setEditPartFactory(new PartFactory());
	}

	@Override
	protected void initializeGraphicalViewer() {
		EObject content = editingDomain.getResourceSet().getResources().get(0)
				.getContents().get(0);
		getGraphicalViewer().setContents(content);
	}

	private void createContents() {
		IRabbitEditorInput editorInput = (IRabbitEditorInput) getEditorInput();
		cdoView = editorInput.getCDOView();
		BasicCommandStack commandStack = (BasicCommandStack) getEditDomain()
				.getCommandStack();
		editingDomain = new TransactionAdapterFactoryEditingDomain(
				adapterFactory, commandStack, cdoView.getResourceSet(), cdoView);
		cdoView.addListener(cdoViewListener);
	}

	@Override
	protected PaletteRoot getPaletteRoot() {
		PaletteRoot root = new PaletteRoot();
		PaletteGroup toolGroup = new PaletteGroup("shapes");

		ToolEntry selectTool = new SelectionToolEntry();
		ToolEntry marqueTool = new MarqueeToolEntry();
		toolGroup.add(selectTool);
		toolGroup.add(marqueTool);

		root.setDefaultEntry(selectTool);
		root.add(toolGroup);

		root.add(createDrawer());
		return root;
	}

	private PaletteEntry createDrawer() {
		PaletteDrawer drawer = new PaletteDrawer("shape");
		CreationToolEntry shapeEntry = new CreationToolEntry("shape", "",
				new RabbitCreationFactory(Shape.class), null, null);
		drawer.add(shapeEntry);

		CreationToolEntry connEntry = new ConnectionCreationToolEntry(
				"connection", "", new RabbitCreationFactory(Connection.class),
				null, null);
		drawer.add(connEntry);
		return drawer;
	}

	@Override
	public boolean isDirty() {
		if (cdoView == null) {
			return false;
		}
		return cdoView.isDirty();
	}

	@Override
	public void doSave(IProgressMonitor monitor) {
		final Map<Object, Object> saveOptions = new HashMap<Object, Object>();
		saveOptions.put(Resource.OPTION_SAVE_ONLY_IF_CHANGED,
				Resource.OPTION_SAVE_ONLY_IF_CHANGED_MEMORY_BUFFER);
		IRunnableWithProgress operation = new IRunnableWithProgress() {
			public void run(IProgressMonitor monitor)
					throws InvocationTargetException, InterruptedException {
				ResourceSet resourceSet = editingDomain.getResourceSet();
				EList<Resource> resources = CDOUtil.getResources(resourceSet);
				monitor.beginTask("", resources.size()); //$NON-NLS-1$
				for (Resource resource : resources) {
					saveOptions.put(CDOResource.OPTION_SAVE_PROGRESS_MONITOR,
							new SubProgressMonitor(monitor, 1));
					saveOptions.put(
							CDOResource.OPTION_SAVE_OVERRIDE_TRANSACTION,
							cdoView);
					try {
						resource.save(saveOptions);
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			}
		};
		try {
			new ProgressMonitorDialog(getSite().getShell()).run(true, true,
					operation);
			((BasicCommandStack) editingDomain.getCommandStack()).saveIsDone();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	@Override
	public ITransactionEditingDomain getEditingDomain() {
		return editingDomain;
	}

	@Override
	public Object getAdapter(Class type) {
		if (type == IPropertySheetPage.class) {
			if (propertySheetPage == null
					|| propertySheetPage.getControl().isDisposed()) {
				propertySheetPage = new TabbedPropertySheetPage(this);
				return propertySheetPage;
			}
		} else if (type == ITransactionEditingDomain.class) {
			return editingDomain;
		}
		return super.getAdapter(type);
	}

	@Override
	public void dispose() {
		if (propertySheetPage != null) {
			propertySheetPage.dispose();
			propertySheetPage = null;
		}
		cdoView.removeListener(cdoViewListener);
		cdoView.close();
		adapterFactory.dispose();
		super.dispose();
	}

	private void cdoViewDirtyStateChanged() {
		getSite().getShell().getDisplay().asyncExec(new Runnable() {
			public void run() {
				firePropertyChange(IEditorPart.PROP_DIRTY);
			}
		});
	}

	@Override
	public String getContributorId() {
		return "org.karelun.rabbit.rcp.propertyContributor";
	}

	@Override
	public boolean isEditable() {
		return !cdoView.isClosed()&&!cdoView.isReadOnly();
	}
}
